業務アプリを長期運用していると、 「列を追加したい」「型を変えたい」「テーブル構造を見直したい」 といったDB更新(マイグレーション)が必ず発生します。
SQLiteは軽量で便利ですが、ALTER TABLEの制約が多く、 安易にスキーマ変更するとデータ破損・不整合・起動不能につながります。
・SQLiteのALTER TABLEの限界
・安全なスキーマ変更パターン(リネーム+コピー)
・バージョン管理(PRAGMA user_version)
・アプリ起動時の自動マイグレーション
・C#でのマイグレーション実装例
・業務アプリ向けベストプラクティス
1. SQLiteのALTER TABLEの限界
SQLiteのALTER TABLEは、他のRDBと比べて機能が限定されています。
■ できること
- 列の追加:
ALTER TABLE ... ADD COLUMN - テーブル名の変更:
ALTER TABLE ... RENAME TO - 列名の変更(新しめのSQLite):
ALTER TABLE ... RENAME COLUMN
■ できない/やりにくいこと
- 列の削除
- 列の型変更
- 制約の変更(PRIMARY KEY, UNIQUE など)
そのため、本格的なスキーマ変更は「新テーブルを作ってコピー」するのが基本になります。
2. 安全なスキーマ変更パターン(リネーム+コピー方式)
SQLite公式が推奨しているのは、次のような手順です。
- 既存テーブルをリネーム
- 新しい定義でテーブルを作成
- 必要な列だけINSERT INTO ... SELECTでコピー
- 旧テーブルを削除
- インデックス・トリガーを再作成
■ 例:Usersテーブルの構造を変更する
-- 1. 旧テーブルをリネーム
ALTER TABLE Users RENAME TO Users_old;
-- 2. 新テーブルを作成
CREATE TABLE Users (
Id INTEGER PRIMARY KEY,
Name TEXT NOT NULL,
Email TEXT,
Age INTEGER NOT NULL DEFAULT 0
);
-- 3. データコピー(列をマッピング)
INSERT INTO Users (Id, Name, Email, Age)
SELECT Id, Name, Email,
COALESCE(Age, 0) -- 旧テーブルにAgeがない場合など
FROM Users_old;
-- 4. 旧テーブル削除
DROP TABLE Users_old;
-- 5. インデックス再作成(必要に応じて)
CREATE INDEX idx_users_name ON Users(Name);
この方式なら、列削除・型変更・制約変更など ALTER TABLEでは難しい変更も安全に行えます。
3. バージョン管理(PRAGMA user_version)
アプリのバージョンアップに合わせてDBスキーマを更新するには、 DB側にも「バージョン」を持たせる必要があります。
■ user_version の設定
PRAGMA user_version = 1;
■ 現在のバージョン取得
PRAGMA user_version;
この値を使って、 「バージョン0 → 1」「1 → 2」 のように 段階的にマイグレーションを実行します。
4. アプリ起動時の自動マイグレーション
業務アプリでは、インストーラーや配布だけでなく、 アプリ起動時に自動でDB更新できると運用が非常に楽になります。
■ C#での基本フロー
int currentVersion;
using (var con = factory.CreateConnection())
{
currentVersion = con.ExecuteScalar<int>("PRAGMA user_version;");
}
if (currentVersion < 1)
{
Migrate_0_to_1();
}
if (currentVersion < 2)
{
Migrate_1_to_2();
}
// ...
■ マイグレーション関数の例
private void Migrate_0_to_1()
{
using var con = factory.CreateConnection();
using var tran = con.BeginTransaction();
con.Execute(@"
CREATE TABLE Users (
Id INTEGER PRIMARY KEY,
Name TEXT NOT NULL
);
", transaction: tran);
con.Execute("PRAGMA user_version = 1;", transaction: tran);
tran.Commit();
}
「バージョンごとに関数を分ける」ことで、 後から見ても分かりやすく、テストもしやすくなります。
5. 既存データを壊さないための注意点
■ 1. 既存列の意味を変えない
同じ列名で意味を変えると、古いデータとの整合性が崩れます。
■ 2. NOT NULL + DEFAULT を活用する
新しい列を追加するときは、DEFAULTを設定しておくと安全です。
ALTER TABLE Users ADD COLUMN Age INTEGER NOT NULL DEFAULT 0;
■ 3. 旧データのマッピングを明示的に書く
INSERT INTO ... SELECT で列を明示的に指定する。
■ 4. バックアップを必ず取る
マイグレーション前にDBファイルをコピーしておくのは鉄則。
6. マイグレーションスクリプトの管理方法
スクリプトはコードと一緒にバージョン管理するのが基本です。
- プロジェクト内に
Migrationsフォルダを作る - バージョンごとにファイルを分ける(例:
V1_Init.sql) - C#側で読み込んで実行する
■ SQLファイルを読み込んで実行する例
var sql = File.ReadAllText("Migrations/V2_AddEmail.sql");
con.Execute(sql, transaction: tran);
これにより、DB定義の変更履歴が明確になります。
7. マイグレーションとテスト戦略
マイグレーションはテストしてから本番適用が必須です。
- テスト用のSQLiteファイルを用意
- 古いバージョンのDBをコピーしてマイグレーション実行
- テーブル構造・データ件数・NULL有無を検証
自動テスト(xUnitなど)に組み込むと安心です。
8. 業務アプリ向けベストプラクティス
- ALTER TABLEだけで何とかしようとしない
- リネーム+新テーブル+コピー方式を基本にする
- PRAGMA user_version でバージョン管理
- アプリ起動時に自動マイグレーション
- マイグレーションはバージョンごとに関数・ファイルを分ける
- 必ずバックアップを取ってから実行する
まとめ:SQLiteのマイグレーションは“慎重に・段階的に・自動化”が正解
- SQLiteはALTER TABLEが弱い → 新テーブル+コピーが基本
- user_versionでバージョン管理すれば、アプリ起動時に自動更新できる
- マイグレーションをコード化・ファイル化しておくと、長期運用が圧倒的に楽になる
「とりあえず手作業でテーブルをいじる」 から卒業して、 「安全に・自動で・再現性のあるマイグレーション」 に移行することで、 SQLiteを使った業務アプリの寿命は大きく伸びます。 この記事をベースに、あなたのプロジェクトに最適なマイグレーション戦略を設計してみてください。